// Copyright (C) Calum Grant 2008

#include "cppscript"
#include "dynamic/extensions.hpp"

static_call( register_pickle_type( cmp_sequence, range_ex(0,0) ) );


namespace dynamic
{
	namespace types
	{
		/// A range iterator.
		class iterator : public dynamic::shared_var_impl
		{
			int m_current, m_step, m_end;

		public:
			iterator(int current, int step, int end) :
				 m_current(current), m_step(step), m_end(end)
			{
			}

			bool as_bool() 
			{ 
				return m_current != m_end; 
			}

			void op_inc() 
			{ 
				m_current += m_step;
			}

			void op_dec()
			{
				m_current -= m_step;
			}

			var key() 
			{ 
				return m_current; 
			}

			var value()
			{
				return m_current;
			}

			void mark_children(gc::garbage_collector &) 
			{ 
			}

			var_cmp_result compare(const var & other)
			{
				iterator * other_iterator = dynamic_cast<iterator*>(other.impl().shared_var());

				if(other_iterator)
				{
					int diff = m_current - other_iterator->m_current;
					return diff<0 ? cmp_lt : diff>0 ? cmp_gt : cmp_equal;
				}
				return cmp_not_equal;
			}

			std::string class_name() { return "range::iterator"; }
		};


		/// Implements a range variable.
		class range_impl : public dynamic::shared_var_impl
		{
			int m_start, m_size, m_step;

		public:
			range_impl(int min, int size, int step) : m_start(min), m_size(size), m_step(step) 
			{ 
			}

			var_cmp_index comparison_index() 
			{ 
				return cmp_sequence; 
			}

			std::string class_name() 
			{
				return "sequence";
			}

			int size()
			{
				return m_size;
			}

			void resize(std::size_t i)
			{
				m_size = i;
			}

			var clone()
			{
				return new range_impl(*this);
			}

			void mark_children(gc::garbage_collector &)
			{
			}

			int as_int()
			{
				return m_start;
			}

			bool as_bool()
			{
				return m_size!=0;
			}

			var keys()
			{
				return new range_impl(0, m_size, 1);
			}

			var values() 
			{
				return this; 
			}

			var_cmp_result compare2(const var & other)
			{
				range_impl * other_impl = dynamic_cast<range_impl*>(other.impl().shared_var());
				if(!other_impl) return cmp_not_equal;	// ?? Throw comparison failure

				if(m_start != other_impl->m_start)
				{
					return m_start < other_impl->m_start ? cmp_lt : cmp_gt;
				}

				if(m_size != other_impl->m_size)
				{
					return m_size < other_impl->m_size ? cmp_lt : cmp_gt;
				}

				if(m_size && m_step != other_impl->m_step)
				{
					return m_step < other_impl->m_step ? cmp_lt : cmp_gt;
				}

				return cmp_equal;
			}

			void output(dynamic::ostream & os)
			{
				os << "sequence(" << m_start << "," << m_size << "," << m_step << ")";
			}

			void output(dynamic::wostream & os)
			{
				os << L"sequence(";
				write_int_to_stream(os, m_start);
				os << L",";
				write_int_to_stream(os, m_size);
				os << L",";
				write_int_to_stream(os, m_step);
				os << L")";
			}

			int get_index(int i)
			{
				return i>=0 ? i : m_size+i;
			}

			var get_member(int i)
			{
				return m_start + get_index(i) * m_step;
			}

			var get_member(const var & v)
			{
				return m_start + get_index(v.as_int()) * m_step;
			}

			var begin() 
			{
				return new iterator(m_start, m_step, m_start+m_step*m_size); 
			}

			var rbegin() 
			{
				return new iterator(m_start + (m_size-1)*m_step, -m_step, m_start-m_step);
			}

			var end() 
			{
				return new iterator(m_start + m_size * m_step, m_step, m_start+m_step*m_size);
			}

			var rend() 
			{
				return new iterator(m_start-m_step, -m_step, m_start-m_step);
			}
		
			var enumerator() 
			{
				return begin();
			}

			var reverse_enumerator() 
			{
				return rbegin();
			}

			void pickle(pickler & p)
			{
				p.write_object_type(cmp_sequence);
				p.write_int(m_start);
				p.write_int(m_size);
				p.write_int(m_step);
			}

			void unpickle(unpickler & p)
			{
				m_start = p.read_int();
				m_size = p.read_int();
				m_step = p.read_int();
			}

			var front() 
			{ 
				return m_size ? var(m_start) : var(); 
			}

			var back() 
			{ 
				return m_size ? var(m_start + (m_size-1)*m_step) : var(); 
			}

			var pop_front()
			{
				if(m_size>0)
				{
					--m_size;
					m_start += m_step;
					return m_start - m_step;
				}
				else
				{
					return var();
				}
			}

			var pop_back()
			{
				if(m_size>0)
				{
					--m_size;
					return m_start + m_size*m_step;
				}
				else
				{
					return var();
				}
			}

			void clear()
			{
				m_size=0;
			}

			bool contains(const var & v)
			{
				int i = v.as_int();
				return i>=0 && i<m_size;
			}

			void op_inc()
			{
				++m_start;
			}

			void op_dec()
			{
				--m_start;
			}

			void assign_add(const var & v)
			{
				m_start += v.as_int();
			}

			void assign_sub(const var & v)
			{
				m_start -= v.as_int();
			}

			void assign_mul(const var & v)
			{
				m_size *= v.as_int();	// ?? v is negative
			}

			var op_add(const var & v)
			{
				return new range_impl(m_start+v.as_int(), m_size, m_step);
			}

			var op_sub(const var & v)
			{
				return new range_impl(m_start-v.as_int(), m_size, m_step);
			}

			var op_mul(const var & v)
			{
				return new range_impl(m_start, m_size*v.as_int(), m_step);
			}

			bool empty()
			{ 
				return m_size==0;
			}

			void get_range(std::size_t size, std::pair<std::size_t,std::size_t> & out)
			{
				out.first = m_start<0 ? size-m_start : m_start;
				int end = m_start + m_size*m_step;
				out.second = end<0 ? size+end : end;
				if(out.first > out.second)
				{
					std::swap(out.first, out.second);
					++out.first;
					++out.second;
				}
			}
		};
	}
};


var dynamic::range(const var & min, const var & max)
{
	int min_i = min.as_int();
	int max_i = max.as_int();

	if(min_i <= max_i)
		return new types::range_impl(min_i, (max_i - min_i)+1, 1);
	else
		return new types::range_impl(min_i, (min_i - max_i)+1, -1);
}


var dynamic::range(const var & min, const var & max, const var & step)
{
	int min_i = min.as_int();
	int max_i = max.as_int();
	int step_i = step.as_int();

	if(min_i <= max_i)
		return new types::range_impl(min_i, (max_i - min_i)/step_i+1, step_i);
	else
		return new types::range_impl(min_i, (min_i - max_i)/step_i+1, -step_i);
}


var dynamic::range_ex(const var & min, const var & max)
{
	int min_i = min.as_int();
	int max_i = max.as_int();
	if(min_i <= max_i)
		return new types::range_impl(min_i, (max_i - min_i), 1);
	else
		return new types::range_impl(min_i, (min_i - max_i), -1);
}


var dynamic::range_ex(const var & min, const var & max, const var & step)
{
	int min_i = min.as_int();
	int max_i = max.as_int();
	int step_i = step.as_int();

	if(min_i <= max_i)
		return new types::range_impl(min_i, (max_i - min_i)/step_i, step_i);
	else
		return new types::range_impl(min_i, (min_i - max_i)/step_i, -step_i);
}


var dynamic::sequence(const var & start, const var & size)
{
	return new types::range_impl(start.as_int(), size.as_int(), 1);
}


var dynamic::sequence(const var & start, const var & size, const var & step)
{
	return new types::range_impl(start.as_int(), size.as_int(), step.as_int());
}
